package html

import "code.google.com/p/go.net/html"

html包实现了兼容HTML5的token(标记)读取器和解析器。

文本的token化是通过使用io.Reader接口创建Tokenizer实现的。编程者有责任保证r提供utf-8编码的HTML文本。

z := html.NewTokenizer(r)

上述代码提供了Tokenizer类型值z,通过重复调用z.Next()可以获取HTML的所有token,该方法会解析下一个token,返回其类型或者一个错误。

for {
	tt := z.Next()
	if tt == html.ErrorToken {
		// ...
		return ...
	}
	// 处理当前token
}

有两组API可以获取当前的token。高水平的API是Token方法,低水平的API是Text或TagName/TagAttr方法。两组API都允许在Next后,Text、Token、TagName或TagAttr之前,调用Raw方法。用EBNF范式表示每个token的合法调用序列如下:

Next {Raw} [ Token | Text | TagName {TagAttr} ]

Token方法返回完全描述一个token的独立的数据结构。(字符)实体(如"<")不会被反转义,标签名和属性的键是小写的,属性会被收集进一个[]Attribute类型字段里。例如:

for {
	if z.Next() == html.ErrorToken {
		// 返回io.EOF表示成功
		return z.Err()
	}
	emitToken(z.Token())
}

低水平API会进行较少的内存申请和拷贝;但是Text、TagName、TagAttr方法返回的[]byte类型值得内容可能在下一次对Next的调用后被修改。例如,要提取一个HTML页面的锚定文本:

depth := 0
for {
	tt := z.Next()
	switch tt {
	case ErrorToken:
		return z.Err()
	case TextToken:
		if depth > 0 {
			// emitBytes应拷贝它接收到的文本,如果不立刻处理文本的话
			emitBytes(z.Text())
		}
	case StartTagToken, EndTagToken:
		tn, _ := z.TagName()
		if len(tn) == 1 && tn[0] == 'a' {
			if tt == StartTagToken {
				depth++
			} else {
				depth--
			}
		}
	}
}

使用io.Reader调用Parse函数可以一次完成解析,并返回解析树(文档元素)的*Node类型根节点。调用者有责任保证Reader接口提供的文本时utf-8编码的。下面是一个采用深度优先顺序处理锚定节点的例子:

doc, err := html.Parse(r)
if err != nil {
	// ...
}
var f func(*html.Node)
f = func(n *html.Node) {
	if n.Type == html.ElementNode && n.Data == "a" {
		// 对n这样那样
	}
	for c := n.FirstChild; c != nil; c = c.NextSibling {
		f(c)
	}
}
f(doc)

相关规范参见:http://www.whatwg.org/specs/web-apps/current-work/multipage/syntax.html

和:http://www.whatwg.org/specs/web-apps/current-work/multipage/tokenization.html

Index

Examples

Variables

var ErrBufferExceeded = errors.New("max buffer exceeded")

ErrBufferExceeded表示超出了缓冲限制。

func EscapeString

func EscapeString(s string) string

EscapeString将特定字符进行转义,如"<"变成"&lt;"。本函数只转义5个字符:<, >, &, '和"。

UnescapeString(EscapeString(s)) == s总是成立,但反过来这不一定。

func UnescapeString

func UnescapeString(s string) string

UnescapeString将转义实体反转义为对应字符,如"&lt;"变成"<"。本函数会反转义远比EscapeString函数转义的字符多得多的字符实体。例如,"&aacute;"反转义为"á",字符实体"&#225;"和"&xE1;"也会反转义为该字符。

type Attribute

type Attribute struct {
    Namespace, Key, Val string
}

Attribute是属性的【名字空间-键-值】三元组。Namespace对外来属性如xlink是非空的,Key是按字符顺序的(因此不含转义字符如'&'、'<'或'>'),Val是解转义的(看起来更类似"a<b"而非"a&lt;b")。

Namespace只用于解析器,token提取器不是使用该字段。

type TokenType

type TokenType uint32

TokenType表示token的类型。

const (
    // ErrorToken表示解析token时出现了错误。
    ErrorToken TokenType = iota
    // TextToken表示文本节点。
    TextToken
    // StartTagToken表示形如<a>的token。
    StartTagToken
    // EndTagToken表示形如</a>的token。
    EndTagToken
    // SelfClosingTagToken表示形如<br/>的自闭合token。
    SelfClosingTagToken
    // CommentToken表示形如<!--x-->的注释。
    CommentToken
    // DoctypeToken表示形如<!DOCTYPE x>的文档说明。
    DoctypeToken
)

func (TokenType) String

func (t TokenType) String() string

String返回TokenType的字符串表示。

type Token

type Token struct {
    Type     TokenType
    DataAtom atom.Atom
    Data     string
    Attr     []Attribute
}

Token包含一个TokenType类型字段和一些信息(起始和结束标签的标签名,文本、注释和文档说明的内容)。标签token还会包含一个属性的切片。所有token的Data字段都是解转义的(其内容更类似"a<b"而非"a&lt;b")。对标签token,DataAtom字段是Data字段的atom信息,如果Data不是已知的标签名,会设置该字段为零值。

func (Token) String

func (t Token) String() string

String返回token的字符串表示。

type Tokenizer

type Tokenizer struct {
    // 内含隐藏或非导出字段
}

Tokenizer返回HTML token流。

func NewTokenizer

func NewTokenizer(r io.Reader) *Tokenizer

NewTokenizer使用r创建并返回一个解析HTML的新*Tokenizer。输入应该是utf-8编码的。

func NewTokenizerFragment

func NewTokenizerFragment(r io.Reader, contextTag string) *Tokenizer

NewTokenizerFragment使用r创建并返回一个解析HTML的新*Tokenizer,以提取一个已存在元素的InnerHTML片段。contextTag是该元素的标签,如"div""或iframe"。

例如,InnerHTML片段"a<b"的token提取依赖于它是一个<p>标签还是一个<script>标签的内容。输入应该是utf-8编码的。

func (*Tokenizer) Buffered

func (z *Tokenizer) Buffered() []byte

Buffered返回一个已经读取进缓冲但尚未处理的数据的切片。

func (*Tokenizer) SetMaxBuf

func (z *Tokenizer) SetMaxBuf(n int)

SetMaxBuf设置提取token时缓冲中数据的数量限制。0表示没有限制。

func (*Tokenizer) AllowCDATA

func (z *Tokenizer) AllowCDATA(allowCDATA bool)

AllowCDATA设置z是否将<![CDATA[foo]]>识别为文本"foo"。默认值是假,表示会将该标签识别为一个伪造注释"<!-- [CDATA[foo]] -->"。

严格来说,HTML兼容的Tokenizer应该当且仅当提取外部内容(如MathML和SVG)的token时允许CDATA。然而,和解析器不同,只使用Tokenizer追踪外部内容是很困难的:一个<svg>标签可能包含一个相对SVG而非相对HTML的外部的<foreignObject>。对于严格兼容HTML5的token提取算法,恰当的调用本函数是使用者的责任。某些情况下,如果不关心MathML或SVG的CDATA是文本还是注释,忽略恰当调用本函数的任务是可以接受的。

func (*Tokenizer) NextIsNotRawText

func (z *Tokenizer) NextIsNotRawText()

NextIsNotRawText方法通知z下一个token不应被视为原始文本。一些元素,如script和title元素,一般要求起始标签后跟的token是原始文本(即不含子元素)。例如,"<title>a<b>c</b>d</title>"进行token的提取会生成起始标签:"<title>",文本:"a<b>c</b>d"和结束标签"</title>"。不会抽提出起始标签"<b>"和结束标签"</b>"。

token提取器的实现一般会在正确的时机查找原始文本。严格来说,HTML5兼容的token提取器不应在外部内容中查找原始文本:<title> 一般需要原始文本,但<svg>中的<title>不需要。另一个例子是<textarea>一般需要原始文本,但<textarea>不允许作为<select>标签的直接子元素;同常解析时,<textarea>隐含着</select>,但在解析<select>的InnerHTML时不能隐式关闭元素。 类似于AllowCDATA方法,和解析器不同,只使用Tokenizer追踪重置原始文本的时机是很困难的。对于严格兼容HTML5的token提取算法, 恰当的调用本函数是使用者的责任。某些情况下,类似AllowCDATA方法,基本使用中忽略恰当调用本函数的任务是可以接受的。

注意,此处"原始文本"的概念和Tokenizer.Raw方法返回的文本是不同的。

func (*Tokenizer) Next

func (z *Tokenizer) Next() TokenType

Next扫描下一个token并返回其类型。

func (*Tokenizer) Raw

func (z *Tokenizer) Raw() []byte

Raw返回当前token未经修改的文本。调用Next、Token、Text、TagName/TagAttr方法可能会修改返回的切片的内容。

func (*Tokenizer) Text

func (z *Tokenizer) Text() []byte

Text返回文本、注释和文档说明token的解转义的内容。返回切片的内容可能会在下一次调用Next方法时改变。

func (*Tokenizer) TagAttr

func (z *Tokenizer) TagAttr() (key, val []byte, moreAttr bool)

TagAttr返回当前标签token下一个未解析的小写的键和解转义的值,以及是否有更多属性。返回切片的内容可能在下一次调用Next方法时被修改。

func (*Tokenizer) TagName

func (z *Tokenizer) TagName() (name []byte, hasAttr bool)

TagName返回标签token的小写的名称(`<IMG SRC="foo">`会返回`img`)以及是否该标签具有属性。返回切片的内容可能在下一次调用Next方法时被修改。

func (*Tokenizer) Token

func (z *Tokenizer) Token() Token

Token返回下一个token。返回值的Data和Attr字段值在之后调用Next方法后仍会保持合法。

func (*Tokenizer) Err

func (z *Tokenizer) Err() error

Err返回最近一次ErrorToken类型token对应的错误。一般是io.EOF,表示token提取结束。

type NodeType

type NodeType uint32

NodeType是Node的类型。

const (
    ErrorNode NodeType = iota
    TextNode
    DocumentNode
    ElementNode
    CommentNode
    DoctypeNode
)

type Node

type Node struct {
    Parent, FirstChild, LastChild, PrevSibling, NextSibling *Node
    Type      NodeType
    DataAtom  atom.Atom
    Data      string
    Namespace string
    Attr      []Attribute
}

Node类型包含一个NodeType字段和一些信息(元素节点的标签名、文本等的内容 )是Node树的一部分。元素节点还有Namespace和Attribute的切片。Data是解转义的,因此其内容更接近"a<b"而非"a&lt;b"。对元素节点,DataAtom是Data的atom,或者零值表示未知标签名。

空的Namespace隐式表示"http://www.w3.org/1999/xhtml"名字空间。

类似的,"math" 是"http://www.w3.org/1998/Math/MathML","svg"是"http://www.w3.org/2000/svg"。

func (*Node) AppendChild

func (n *Node) AppendChild(c *Node)

AppendChild将c添加为n的子节点。如果c已经有父节点和兄弟节点,会panic。

func (*Node) InsertBefore

func (n *Node) InsertBefore(newChild, oldChild *Node)

InsertBefore向n的子节点序列中oldChild的前面插入newChild作为其子节点。如果oldChild为nil,会将newChild添加到子节点序列的结尾。如果newChild已经有父节点和兄弟节点,会panic。

func (*Node) RemoveChild

func (n *Node) RemoveChild(c *Node)

RemoveChild删除n的子节点c。删除后,c将没有父节点和兄弟节点。如果c不是n的子节点,会panic。

func Parse

func Parse(r io.Reader) (*Node, error)

Parse函数返回从r中读取的HTML文档的解析书。输入必须是utf-8编码的。

Example
s := `<ul><li><a href="foo">Foo</a><li><a href="/bar/baz">BarBaz</a></ul>`
doc, err := html.Parse(strings.NewReader(s))
if err != nil {
    log.Fatal(err)
}
var f func(*html.Node)
f = func(n *html.Node) {
    if n.Type == html.ElementNode && n.Data == "a" {
        for _, a := range n.Attr {
            if a.Key == "href" {
                fmt.Println(a.Val)
                break
            }
        }
    }
    for c := n.FirstChild; c != nil; c = c.NextSibling {
        f(c)
    }
}
f(doc)

Output:

foo
/bar/baz

func ParseFragment

func ParseFragment(r io.Reader, context *Node) ([]*Node, error)

ParseFragment解析一个HTML片段,返回发现的节点。如果该片段式已存在的元素context的InnerHTML,会将解析出的元素传递给context。

func Render

func Render(w io.Writer, n *Node) error

Render将解析树n翻译后写入接口w中。

翻译是在'最大努力'模块上进行的:对Render函数生成的输出调用Parse函数总是生成某些元素和原始的解析树类似的树,但只有原始解析树是格式正确的情况下,才会也确定会保证Parse返回的解析树是n的精确克隆。格式正确并不容易说明,兼容HTML5规范。

对任意输入调用Parse函数一般总是生成格式正确的解析树。但是,也有可能生成格式错误的解析树。例如,在一个格式正确的解析树中,没有<a>元素是另一个<a>元素的子元素:解析"<a><a>"生成两个兄弟元素。类似的,格式正确的解析树中,没有<a>元素是<table>元素的子元素:解析"<p><table><a>"生成一个具有两个互为兄弟元素的子元素的<p>元素;<a>元素的父元素被重设为<table>元素的父元素。但是,对"<a><table><a>"调用Parse函数不会返回错误,但是结果会含有一个具有<a>子元素的<a>元素,导致格式错误。

从编程角度看,生成的树仍然是格式正确的。然而,有可能构造出一个看起来无害的树,但在翻译后再解析时却会生成不同的树。一个简单的例子,一个单独的文本元素会变成一个包含<html>、<head>和<body>元素的树。另一个例子, 标题等价的"a<head>b</head>c"会变成"<html><head><head/><body>abc</body></html>"。